package com.xsomnus.rpc.provider.server;

import com.alibaba.fastjson.JSON;
import com.xsomnus.rpc.common.common.Constants;
import com.xsomnus.rpc.common.entity.Request;
import com.xsomnus.rpc.common.entity.Response;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.util.Map;

/**
 * @author @xsomnus666_xiawenye★
 * @since 2019/4/22 0022 14:16
 * 当你发现自己的才华撑不起野心时，就请安静下来学习吧
 */
@Slf4j
@ChannelHandler.Sharable
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    private final Map<String, Object> serviceMap;

    public NettyServerHandler(Map<String, Object> serviceMap) {
        this.serviceMap = serviceMap;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.debug("客户端连接成功! {}", ctx.channel().remoteAddress());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.debug("客户端断开连接!, {}", ctx.channel().remoteAddress());
        ctx.channel().close();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Request request = JSON.parseObject(msg.toString(), Request.class);
        if (Constants.HEART_BEAT_METHOD.equals(request.getMethodName())) {
            log.debug("客户端的心跳信息: {}", ctx.channel().remoteAddress());
        } else {
            log.debug("RPC客户端请求的接口: {}, 请求的方法名: {}", request.getClassName(), request.getMethodName());
            Response response = new Response();
            response.setRequestId(request.getId());
            try {
                Object result = this.handler(request);
                response.setData(result);
            } catch (Throwable throwable) {
                throwable.printStackTrace();
                response.setCode(Constants.ERROR_CODE);
                response.setErrMsg(throwable.toString());
                log.error("RPC调用方法出错, 原因: {}", throwable.toString());
            }
            log.debug("response: {}", response);
            ctx.writeAndFlush(response);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error(cause.getMessage());
        ctx.close();
    }


    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            if (idleStateEvent.state().equals(IdleState.ALL_IDLE)) {
                log.debug("客户端已超过60秒未进行读写数据, 关闭连接: {}", ctx.channel().remoteAddress());
                ctx.channel().close();
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    /**
     * 获取请求实体之后
     * 通过反射获取本地方法的结果
     *
     * @param request 请求实体
     */
    private Object handler(Request request) throws Throwable {
        log.debug("[server] request: {}", request);
        String className = request.getClassName();
        Object serviceBean = serviceMap.get(className);
        if (null != serviceBean) {
            Class<?> serviceClass = serviceBean.getClass();
            String methodName = request.getMethodName();
            Class<?>[] parameterTypes = request.getParameterTypes();
            Object[] parameters = request.getParameters();
            Method method = serviceClass.getMethod(methodName, parameterTypes);
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }
            return method.invoke(serviceBean, rebuildParameters(parameterTypes, parameters));
        } else {
            throw new Exception(String.format("未找到服务接口, 请检查配置! errMsg: %s, methodName: %s", className, request.getMethodName()));
        }
    }

    private Object[] rebuildParameters(Class<?>[] parameterTypes, Object[] parameters) {
        if (parameters == null || parameters.length == 0) {
            return parameters;
        } else {
            Object[] newParameters = new Object[parameters.length];
            for (int i = 0; i < parameters.length; i++) {
                newParameters[i] = JSON.parseObject(parameters[i].toString(), parameterTypes[i]);
            }
            return newParameters;
        }
    }

}
